package com.pan.studentmaster;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;

public class Turnable extends View implements View.OnLayoutChangeListener {

	public Turnable(Context context) {
		super(context);
		initialize(context);
	}

	public Turnable(Context context, AttributeSet attrs) {
		super(context, attrs);
		initialize(context);
	}

	public Turnable(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		initialize(context);
	}
	
	public boolean turning() { return _turningForward || _turningBackward; }
	public boolean turningForward() { return _turningForward; }
	public boolean turningBackward() { return _turningBackward; }
	
	public Bitmap getNextBitmap() { return _next; }
	public Bitmap getPrevBitmap() { return _prev; }
	public void setListener(TurnableListener listener) { _listener = listener; }
	public void setInterval(int interval) { _interval = interval; }
	public void setStep(int step) { _step = step; }
	
	public void turnForward(Bitmap next) {
		if(_turningForward || _turningBackward)
			return;
		_next = Bitmap.createScaledBitmap(next, _width, _height, true);
		_turningForward = true;
		onTurnStart();
		SharedPreferences prefs = Main.instance().getSharedPreferences(Main.APP_NAME, Context.MODE_PRIVATE);
		if(prefs.getBoolean("FAST_PAGE", true)) {
			onTurnFinish();
			return;
		}
		_all = new RectF(0, 0, _width, _height);
		_x = _width;
		_y = _height;
		_page1Width = _width;
		_maxRadius = _width / 10f;
		_pathPage1Shadow = new Path();
		_pathPage1Back = new Path();
		_pathPage2 = new Path();
		_pathPage2Shadow = new Path();
		_page1ShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, _PAGE1SHADOW);
		_page1ShadowDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
		_page1BackDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, _PAGE1BACK);
		_page1BackDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
		_page2ShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, _PAGE2SHADOW);
		_page2ShadowDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
		_step = 0 - Math.abs(_step);
		_stage = 1;
		step();
	}
	
	public void turnBackward(Bitmap prev) {
		if(_turningBackward || _turningForward)
			return;
		_prev = Bitmap.createScaledBitmap(prev, _width, _height, true);
		_turningBackward = true;
		onTurnStart();
		SharedPreferences prefs = Main.instance().getSharedPreferences(Main.APP_NAME, Context.MODE_PRIVATE);
		if(prefs.getBoolean("FAST_PAGE", true)) {
			onTurnFinish();
			return;
		}
		_all = new RectF(0, 0, _width, _height);
		_x = 0;
		_y = _height;
		_page1Width = 0;
		_maxRadius = _width / 10f;
		_pathPage1Shadow = new Path();
		_pathPage1Back = new Path();
		_pathPage2 = new Path();
		_pathPage2Shadow = new Path();
		_page1ShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, _PAGE1SHADOW);
		_page1ShadowDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
		_page1BackDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, _PAGE1BACK);
		_page1BackDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
		_page2ShadowDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, _PAGE2SHADOW);
		_page2ShadowDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
		_step = Math.abs(_step);
		_stage = 1;
		step();
	}
	
	protected void onTurnStart() {
		if(_listener != null)
			_listener.onTurnStart(this);
	}
	
	protected void onTurnFinish() {
		if(_listener != null)
			_listener.onTurnFinish(this);
		if(_turningForward) {
			_prev = _current;
			_current = _next;
			_next = null;
		}
		else if(_turningBackward) {
			_next = _current;
			_current = _prev;
			_prev = null;
		}
		_turningForward = false;
		_turningBackward = false;
		setBackground(new BitmapDrawable(Main.instance().getResources(), _current));
	}
	
	protected Bitmap			_current = null;
	protected Bitmap			_prev = null;
	protected Bitmap			_next = null;
	protected int				_width;
	protected int				_height;
	protected boolean			_turningBackward = false;
	protected boolean			_turningForward = false;
	
	// private
	
	private int					_interval = 2;
	private int					_step = -10;
	private TurnableListener	_listener = null;
	
	private final TurnerHandler	_HANDLER = new TurnerHandler();
	private final float			_ANGLE = 20f / 180f * (float)Math.PI;
	private final int[]			_PAGE1SHADOW = new int[] { 0x0, 0xFF000000 };
	private final int[]			_PAGE1BACK = new int[] { 0xFF808080, 0xFF000000 };
	private final int[]			_PAGE2SHADOW = new int[] { 0xFF000000, 0x00000000};
	
	private float				_maxRadius;
	private float				_x;
	private float				_y;
	private float				_radius;
	private float				_page1Width;
	private RectF				_all = null;
	private Path				_pathPage1Shadow = null;
	private Path				_pathPage1Back = null;
	private Path				_pathPage2 = null;
	private Path				_pathPage2Shadow = null;
	// drawables
	private GradientDrawable	_page1ShadowDrawable = null;
	private GradientDrawable	_page1BackDrawable = null;
	private GradientDrawable	_page2ShadowDrawable = null;
	// stage:
	// 1: from start to the right up corner is involved
	// 2: up to oval disappears
	// 3: up to finish
	// 4: finish, cleanup
	private int					_stage = 0;
	
	private void initialize(Context context) {
		// some new machine uses hardware accelerator and does not support software clipPath
		// so have to make it software rendered
		if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
			setLayerType(View.LAYER_TYPE_SOFTWARE, null);
		}
		addOnLayoutChangeListener(this);
	}
	
	@Override
	public void onLayoutChange(View v, int left, int top, int right,
			int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
		_width = right;
		_height = bottom;
	}
	
	private void step() {
		switch(_stage) {
			case 1:
				_x += _step;
				_radius = (float)(_turningForward ? (_width - _x) / Math.PI : _x / Math.PI);
				_y = _height - _radius - _radius;
				RectF rect1 = new RectF(_x - _radius, _y, _x + _radius, _height);
				_pathPage1Shadow.reset();
				_pathPage1Shadow.moveTo(_x, _y);
				_pathPage1Shadow.arcTo(rect1, 270, _turningForward ? 180 : -180);
				_pathPage1Shadow.close();
				_pathPage1Back.reset();
				_pathPage1Back.moveTo(_x, _y);
				_pathPage1Back.arcTo(rect1, 270, _turningForward ? 90 : -90);
				float y2 = _y - (float) ((_turningForward ? _width - _x : _x) / Math.tan(_ANGLE));
				if(y2 <= 0) {
					y2 = 0;
					_stage = 2;
				}
				_pathPage1Back.lineTo(_turningForward ? _width : 0, y2);
				_pathPage1Back.close();
				_pathPage2.reset();
				_pathPage2.moveTo(_turningForward ? _width : 0, _height);
				_pathPage2.lineTo(_x, _height);
				_pathPage2.arcTo(rect1, 90, _turningForward ? -90 : 90);
				_pathPage2.lineTo(_turningForward ? _width : 0, y2);
				_pathPage2.close();
				_pathPage2Shadow.addPath(_pathPage2);
				if(_turningForward)
					postInvalidate((int)_x, (int)y2, _width, _height);
				else
					postInvalidate(0, (int)y2, (int)_x, _height);
				break;
			case 2:
			case 3:
				float page1Width = _page1Width; // keep old _page1Width and use it in invalidate
				_x += _step;
				if(_radius < _maxRadius) {
					_radius = (float)((_turningForward ? _width - _x : _x) / Math.PI);
					_y = _height - _radius - _radius;
				}
				RectF rect2 = new RectF(_x - _radius, _y, _x + _radius, _height);
				if(_stage == 2) {
					_pathPage1Shadow.reset();
					_pathPage1Shadow.moveTo(_x, _y);
					_pathPage1Shadow.arcTo(rect2, 270, _turningForward ? 180 : -180);
					_pathPage1Shadow.close();
					if((_turningForward && _x + _radius <= 0) || (!_turningForward && _x - _radius >= _width))
						_stage = 3;
				}
				else {
					if(_pathPage1Shadow != null)
						_pathPage1Shadow = null;
					if((_turningForward && _page1Width <= 0) || (!_turningForward && _page1Width >= _width))
						_stage = 4;
				}
				_pathPage1Back.reset();
				_pathPage1Back.moveTo(_x, _y);
				_pathPage1Back.arcTo(rect2, 270, _turningForward ? 90 : -90);
				float page1WidthDelta = (float)(_y * Math.tan(_ANGLE));
				_page1Width = _turningForward ? _x + page1WidthDelta : _x - page1WidthDelta;
				_pathPage1Back.lineTo(_page1Width, 0);
				_pathPage1Back.close();
				_pathPage2.reset();
				_pathPage2.moveTo(_turningForward ? _width : 0, _height);
				_pathPage2.lineTo(_x, _height);
				_pathPage2.arcTo(rect2, 90, _turningForward ? -90 : 90);
				_pathPage2.lineTo(_page1Width, 0);
				_pathPage2.lineTo(_turningForward ? _width : 0, 0);
				_pathPage2.close();
				_pathPage2Shadow.reset();
				_pathPage2Shadow.moveTo(_page1Width, _height);
				_pathPage2Shadow.lineTo(_x, _height);
				_pathPage2Shadow.arcTo(rect2, 90, _turningForward ? -90 : 90);
				_pathPage2Shadow.lineTo(_page1Width, 0);
				_pathPage2Shadow.close();
				if(_turningForward)
					postInvalidate((int)_x, 0, (int)page1Width, _height);
				else
					postInvalidate((int)page1Width, 0, (int)_x, _height);
				break;
			case 4:
				// finished, cleanup and stop
				_all = null;
				_pathPage1Back = null;
				_pathPage2 = null;
				_pathPage2Shadow = null;
				_page1ShadowDrawable = null;
				_page1BackDrawable = null;
				_page2ShadowDrawable = null;
				System.gc();
				onTurnFinish();
				break;
		}
		if(turning()) // if reaches stage 4, flags will be reset in onTurnFinish
			_HANDLER.sleep(_interval);
	}
	
	@Override
	public void onDraw(Canvas canvas) {
		if(_pathPage2 != null) {
			canvas.save();
			if(canvas.clipPath(_pathPage2)) {
				canvas.drawBitmap(_turningForward ? _next : _prev, null, _all, null);
			}
			canvas.restore();
		}
		if(_pathPage2Shadow != null) {
			canvas.save();
			if(canvas.clipPath(_pathPage2Shadow)) {
				if(_turningForward)
					_page2ShadowDrawable.setBounds((int)_x, 0, (int)(_page1Width), (int)_height);
				else
					_page2ShadowDrawable.setBounds((int)_page1Width, 0, (int)_x, (int)_height);
				_page2ShadowDrawable.draw(canvas);
			}
			canvas.restore();
		}
		if(_pathPage1Shadow != null) {
			canvas.save();
			if(canvas.clipPath(_pathPage1Shadow)) {
				if(_turningForward)
					_page1ShadowDrawable.setBounds((int)_x, (int)_y, (int)(_x + _radius), _height);
				else
					_page1ShadowDrawable.setBounds((int)(_x - _radius), (int)_y, (int)_x, _height);
				_page1ShadowDrawable.draw(canvas);
			}
			canvas.restore();
		}
		if(_pathPage1Back != null) {
			canvas.save();
			if(canvas.clipPath(_pathPage1Back)) {
				if(_turningForward)
					_page1BackDrawable.setBounds((int)_x, 0, (int)(_page1Width), _height);
				else
					_page1BackDrawable.setBounds((int)_page1Width, 0, (int)_x, _height);
				_page1BackDrawable.draw(canvas);
			}
			canvas.restore();
		}
	}
	
	// Inner class to turn the page in a fixed rate
	class TurnerHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			Turnable.this.step();
		}

		public void sleep(long millis) {
			removeMessages(0);
			sendMessageDelayed(obtainMessage(0), millis);
		}
	}

}
